iT邦幫忙

2023 iThome 鐵人賽

DAY 12
4
Software Development

Spring Boot 零基礎入門系列 第 12

Spring Boot 零基礎入門 (12) - Spring AOP 的用法 - @Aspect

  • 分享至 

  • xImage
  •  

賀!此系列文榮獲 2023 iThome 鐵人賽《優選》獎項,正在規劃出書中,感謝大家的支持🙏,同名課程「Java 工程師必備!Spring Boot 零基礎入門」也已在 Hahow 平台上架

哈囉大家好,我是古古

在上一篇文章中,我們已經了解了 Spring AOP 的概念和原理,那麼這篇文章,我們就會接著來介紹,要如何在 Spring Boot 中使用 Spring AOP 的功能

回顧:什麼是 Spring AOP?


上一篇文章中有提到,AOP 的全稱是 Aspect-Oriented Programming,中文是翻譯為「切面導向程式設計」或是「剖面導向程式設計」,而 AOP 的概念,就是「透過切面,統一的去處理方法之間的共同邏輯」

因此當我們使用了 AOP 之後,就再也不用去複製貼上程式了,我們只需要在切面裡面寫好測量時間的程式,之後就可以在任何地方去使用這個切面,讓這個切面替我們完成測量時間的功能了

https://ithelp.ithome.com.tw/upload/images/20230927/20151036iiFH0oCP5v.png

在 pom.xml 載入 Spring AOP


如果想要在 Spring Boot 中使用 Spring AOP 的功能的話,首先會需要在 pom.xml 這個檔案裡面新增以下的程式,將 Spring AOP 的功能給載入進來

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

加完上述的程式之後,在 pom.xml 的右上角會出現一個 M 符號,點擊這個 M 符號,就可以更新這個 Spring Boot 程式,把 Spring AOP 的功能給載入進來

https://ithelp.ithome.com.tw/upload/images/20230927/20151036GAPVmPzy5s.png

製造切面的方法:@Aspect


加好 Spring AOP 的功能之後,我們就可以來使用 Spring AOP 的專屬註解了!

如果我們想要使用 Spring AOP,去創造一個新的切面出來的話,那我們就可以在 class 上面加上 @Aspect,這樣子就可以成功創建一個切面出來了

譬如說我們可以先創建一個新的 class 叫做 MyAspect,然後在上面加上 @Aspect,這樣就可以將 MyAspect 變成是一個切面了

https://ithelp.ithome.com.tw/upload/images/20230927/20151036IvTX5nMdK0.png

不過在使用 @Aspect 去創建新切面時,一定要特別注意,只有 Bean 才可以變成一個切面,所以換句話說的話,就是在使用 @Aspect 時,也要同時使用 @Component,將該 class 變成 Bean 的同時,切面的設定才會生效,如果只有在 class 上面加上 @Aspect 的話,是完全沒有任何效果的!

反正無論如何,大家只要記得,在使用切面時,需要「@Component@Aspect 要一起使用」就對了

在切入點方法「執行前」執行切面:@Before


創建好切面 MyAspect 這個 class 之後,我們就可以在這個 class 裡面去撰寫切面方法,並且指定要在什麼時機點,去切入方法了

舉例來說,我們可以在 MyAspect class 裡面,先寫上一個 before() 方法,然後在這個 before() 方法裡面,輸出一行「I'm before」的訊息到 console 上

https://ithelp.ithome.com.tw/upload/images/20230927/20151036wTmVKyEYyF.png

接著,只要我們在這個 before() 方法的上面,去加上一個 @Before,並且在後面指定想要的切入點,就可以在這個切入點的方法 「執行前」,去執行這個 MyAspect class 中的 before() 方法了

https://ithelp.ithome.com.tw/upload/images/20230927/20151036ASpGGuZ7IR.png

那上面那樣的講法可能有點抽象,所以我們可以再試著來拆解一下這段程式,了解怎麼解讀這些 AOP 的程式

如何解讀 AOP 程式?


到目前為止,我們有在 MyAspect class 裡面寫上了一個 before() 方法,並且在他上面去加上了一個 @Before,那這一段程式是可以拆成三個步驟來解讀的:

步驟一:先閱讀 @Before 小括號中的程式

@Before 後面的小括號中的程式,稱為「切入點 (Pointcut)」,即是去指定哪個方法要被切

舉例來說,假設我們想要測量的是 HpPrinter 中的方法的時間,那麼切面就是測量時間的程式,而 HpPrinter 中的方法,就是切入點 (Pointcut)

所以在 @Before 後面的小括號中的程式,即是去指定「切入點 (Pointcut)」,表示我們想要讓哪個方法,最後會被 MyAspect 這個切面所切

https://ithelp.ithome.com.tw/upload/images/20230927/20151036bCQ3X6IU1d.png

步驟二:查看前面的註解是什麼

確認好了切入點之後,接著就是查看前面所加上的註解是什麼,像是這邊所加上的,就是 @Before,而 @Before 的用途,就是指定要在小括號中的切入點 「執行前」,去執行下面的 before() 方法

所以簡單的說的話,前面的這個 @Before,他指定的就是 「時機點」@Before 對應的就是切入點方法「執行前」執行,AOP 還有提供其他時機點的不同註解,像是 @After@Around,後續也會再跟大家做介紹

因此在步驟二這裡,就是去確認「切面方法執行的時機點」

https://ithelp.ithome.com.tw/upload/images/20230927/20151036s1vM9OuS4v.png

步驟三:要執行的切面方法

當我們確認好「切入點」和「時機點」之後,最後就是在下面的 before() 方法中,去撰寫切面的程式

像是在這裡我們就只在 before() 方法裡面寫上一行程式,去輸出「I'm before」的資訊到 console 上

https://ithelp.ithome.com.tw/upload/images/20230927/20151036hZkh81qNRO.png

小結:綜合上述的三個步驟

所以綜合上述的三個步驟,就可以去解讀這一段 AOP 的程式的含義是什麼了

  • 步驟一:我們指定了「切入點為 HpPrinter 中的所有方法」
  • 步驟二:在切入點的方法「執行之前」
  • 步驟三:執行下面的 before() 方法

因此最後的結果,就會長的像是下圖這樣:

https://ithelp.ithome.com.tw/upload/images/20230927/20151036NvxaPTIZBB.png

在 Spring Boot 中練習 @Aspect 和 @Before


看完了上述對 @Aspect@Before 的介紹,我們也可以直接到 Spring Boot 程式中添加這些程式,實際的感受一下,Spring AOP 這個切面的邏輯到底是怎麼個運作法

首先我們可以把之前的 HpPrinter 給刪減一下,將 count 變數的相關程式刪掉,只留下輸出「HP 印表機: xxx」的程式即可

@Component
public class HpPrinter implements Printer {

    @Override
    public void print(String message) {
        System.out.println("HP 印表機: " + message);
    }
}

接著同樣是在 com.example.demo 這個 package 底下,創建一個新的 MyAspect class 出來,並且添加下列的程式

@Aspect
@Component
public class MyAspect {

    @Before("execution(* com.example.demo.HpPrinter.*(..))")
    public void before() {
        System.out.println("I'm before");
    }
}

接著只需要確認 MyController 中有成功注入 HpPrinter 進來,並且有去呼叫到 print() 方法,這樣就可以去運行 Spring Boot 程式了

@RestController
public class MyController {

    @Autowired
    private Printer printer;

    @RequestMapping("/test")
    public String test() {
        printer.print("Hello World");
        return "Hello World";
    }
}

當 Spring Boot 程式運行成功之後,大家可以訪問 http://localhost:8080/test ,理論上會出現和之前的文章中的運行結果一樣,都是在瀏覽器中出現一個「Hello World」的字串

https://ithelp.ithome.com.tw/upload/images/20230927/20151036rqnUcsx40w.png

而在 IntelliJ 的 console 中,則可以看到出現了兩行字,分別是「I'm before」以及「HP 印表機: Hello World」,所以這就表示 MyAspect 中的 before() 切面方法,就確實的在 HpPrinter 的 print() 方法前執行了,這就是 Spring AOP 幫助我們做到的

https://ithelp.ithome.com.tw/upload/images/20230927/20151036AHZGbvOquy.png

因此透過這個例子,大家就可以體會到 Spring AOP 的強大功能,當有了 Spring AOP 之後,我們就不需要將所有程式通通都塞到 print() 方法裡面,而是可以將共用的部分拆分出來,統一的寫在切面中進行管理

所以以後當哪裡有需要這個共同邏輯,我們就可以去重複使用這個切面,這樣子不僅可以達到程式重複利用的好處,也可以讓各個 class 更專注的在處理他的功能,而不是添加一堆其他程式了

其他用法:@After、@Around


在 Spring AOP 中,除了可以使用 @Before 達到在方法「執行前」執行切面之外,我們也是可以將 @Before 替換成 @After 或是 @Around,在不同的時機點去執行切面的

在 Spring AOP 裡面,有三種時機點可以選擇:

  • @Before:在方法「執行前」執行切面
  • @After:在方法「執行後」執行切面
  • @Around:在方法「執行前」和「執行後」,執行切面

@After 的用法

@After 的寫法其實和 @Before 一模一樣,只要把 @Before 換成 @After 就可以用了,所以實際使用起來會是下面這個樣子

@Aspect
@Component
public class MyAspect {

    @After("execution(* com.example.demo.HpPrinter.*(..))")
    public void after() {
        System.out.println("I'm after");
    }
}

@Around 的用法

@Around 因為寫起來比較複雜,因此此處提供程式給大家參考

@Aspect
@Component
public class MyAspect {

    @Around("execution(* com.example.demo.HpPrinter.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("I'm around before");

        // 執行切入點的方法,obj 為切入點方法執行的結果
        Object obj = pjp.proceed();

        System.out.println("I'm around after");
        return obj;
    }
}

補充 1:切入點 (Pointcut) 怎麼撰寫?


在前面的程式中,我們有在 @Before 後面加上一段看起來很長的程式,那一段程式就是在指定方法的切入點為何

https://ithelp.ithome.com.tw/upload/images/20230927/20151036RzAq1RXXkp.png

切入點的寫法是有一定規則寫法的,像是上面的這段程式,就是在表示切入點為「HpPrinter 的所有方法」

不過由於這個切入點寫起來還滿複雜的,而且使用頻率也不是很高,因此就建議大家有用到的時候再去查詢就可以了,以下提供幾種常見的寫法邏輯給大家:

  1. 切入點為 com.example.demo.HpPrinter 底下的 print() 方法
execution(* com.example.demo.HpPrinter.print())
  1. 切入點為 com.example.demo.HpPrinter 底下的所有方法
execution(* com.example.demo.HpPrinter.*(..))
  1. 切入點為 com.example.demo 這個 package 中的所有 class 的所有方法(不包含子 package)
execution(* com.example.demo.*(..))
  1. 切入點為 com.example.demo 這個 package 及其底下所有子 package 中的所有 class 的所有方法
execution(* com.example.demo..*(..))
  1. 切入點為那些帶有 @MyAnnotation 的方法
@annotation(com.example.demo.MyAnnotation)

補充 2:Spring AOP 的發展


了解了 Spring AOP 的用法之後,最後跟大家介紹一下 Spring AOP 的相關發展

Spring AOP 以前最常被用在以下三個地方:

  1. 權限驗證
  2. 統一的 Exception 處理
  3. log 記錄

但是由於 Spring Boot 發展逐漸成熟,因此上述這些功能,都已經被封裝成更好用的工具讓我們使用了,所以大家目前已經比較少直接使用 @Aspect 去創建一個切面出來了

舉例來說,像是權限驗證這一塊,我們就會改成使用 Spring Security 這個工具來完成,不過 Spring Security 的底層仍舊是透過 Spring AOP 來完成的,只是 Spring Security 把他封裝得更好、使用上更方便,因此在進行權限驗證時,使用 Spring Security 是更能提升大家開發的效率的

所以雖然 Spring AOP 已經漸漸淡出大家的日常使用,不過他作為 Spring 框架中的重要特性之一,還是常常生活在我們周邊的,只是我們可能感覺不太到而已XD

因此上述所介紹的 Spring AOP 的相關用法,大家就有個印象就可以了,重點是要把 AOP 的切面概念搞懂,至於其他的 @Before@After@Around 的用法,就有個印象就可以了

總結


這篇文章介紹了要如何透過 @Aspect@Before,使用切面去切入指定的切入點,在 Spring Boot 中使用 Spring AOP 的功能,並且也簡單介紹了另外兩個時機點的註解 @After@Around,最後也補充了切入點的撰寫方式,以及 Spring AOP 的相關發展

那麼有關 Spring AOP 的介紹就到這邊結束了,Spring AOP 作為 Spring 框架中的重要特性之一,縱使他已經在日常開發中很少用到,但是 AOP 切面的概念,仍舊是在許多功能中被廣泛應用的

那所以下一篇文章,我們就會進入到下一個章節:Spring MVC,來了解要如何在 Spring Boot 中和「前端」進行溝通,那我們就下一篇文章見啦!

相關連結



上一篇
Spring Boot 零基礎入門 (11) - Spring AOP 簡介
下一篇
Spring Boot 零基礎入門 (13) - Spring MVC 簡介
系列文
Spring Boot 零基礎入門30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
凱文大叔
iT邦新手 4 級 ‧ 2023-11-04 09:46:24

系統中要製作plugin也可以使用AOP的特性開發

我要留言

立即登入留言